const _ = require('lodash')
const path = require('path')
const Ajv = require('ajv')
const utils = require('./utils')
const loadTemplate = require('./loadTemplate')
const loadData = require('./loadData')
const startService = require('./client').start
const templates = loadTemplate(path.join(__dirname, '../template'))
const datas = loadData(path.join(__dirname, '../data'))
const loadHandle = require('./loadHandle')
const handles = loadHandle(path.join(__dirname, '../handle'))

var _nodes = []
var _client = null
var ajvSet = new Ajv({ useDefaults: true })
var ajvCheck = new Ajv({ removeAdditional: true })

function findServiceNode(serviceId) {
  return _.find(_nodes, {
    serviceId
  })
}

function addServiceNode(serviceNode) {
  const topics = serviceNode.topics
  _.forEach(topics, function(topic) {
    addTopic(topic)
  })
  _nodes.push(serviceNode)
}

function makeServiceId(serviceType, serviceVersion, serviceName) {
  if (!serviceName) {
    return serviceType + '/' + serviceVersion
  } else {
    return serviceType + '/' + serviceVersion +'/' + serviceName
  }
}

function getBaseTopicName(topicName) {
  if (_.startsWith(topicName, '$')) {
    return topicName
  } else {
    return topicName.split('/').slice(2).join('/')
  }
}

var _topics = {}
function findTopic(topicName) {
  return _topics[topicName]
}

function addTopic(topic) {
  _topics[topic.name] = topic
}

function updateTopic(topic, action, params) {
  const actions = topic.actions
  if (actions && actions.hasOwnProperty(action)) {
    const data = _.cloneDeep(topic.data)
    actions[action].call(topic, data, params)
    return data
  } else {
    return null
  }
}

function publishTopic(topicName, message) {
  if (_client && topicName && message) {
    _client.publish(topicName, JSON.stringify(message), {qos: 0, retain: true})
  }
}

Service.find = findServiceNode

Service.start = function() {
  console.log('start')
  startService({
    connect: function(client) {
      console.log('connect')
      _.forEach(_nodes, function(serviceNode) {
        client.subscribe('+/+/' + serviceNode.serviceId + '/#')
        if(_.includes(serviceNode.serviceId.split('/'), '$timer')) {
          console.log('delay')
          const service = _.find(handles, {
            type: '$timer'
          })
          if(service) {
            service.handle(client, serviceNode.serviceId, serviceNode.schema, serviceNode.topics)
          } else {
            console.log('invalid cfg')
          }
        } else {
          action(client, serviceNode.topics)
        }
      })
      _client = client
    },
    message: function(client, topicName, payload) {
      console.log('message')
      if(_.includes(topicName.split('/'), '$timer')) {
        const service = _.find(handles, {
          type: '$timer'
        })
        if(service) {
          service.handle(client, topicName, JSON.parse(payload).enable)
        }
        return
      }
      try {
        const message = JSON.parse(payload) 
        console.log(message)
        console.log(topicName)
        const baseTopicName = getBaseTopicName(topicName)
        const topic = findTopic(baseTopicName)
        if (topic) {
          if (!topic.check(message)) {
            console.log('invalid payload: ' + payload)
            return
          }
          topic.message && topic.message.call(topic, topicName, message)

          if (topic.topic.retain && !_.isEqual(topic.data, message)) {
            topic.data = message
            console.log('021')
            client.publish(topic.name, JSON.stringify(topic.data))
          }
        }
      } catch(e) {
        console.log('666')
        console.error(e)
      }
    }
  })
}

function action(client, topics) {
  _.forEach(topics, function(topic) {
    const service = _.find(datas, {
      name: topic.name
    })
    if (topic.topic.retain) {
      console.log(topic.name)
      if(service && topic.check(service.payload)) {
        client.publish(topic.name, JSON.stringify(service.payload), {qos: 0, retain: true})
      } else {
        client.publish(topic.name, JSON.stringify(topic.data), {qos: 0, retain: true})
      } 
    }
  })
}

function extend(a, b) {
  Object.keys(b).forEach(function(k) {
    if (!a[k]) {
      a[k] = b[k];
    }
  });
  return a;
}

function newTopic(serviceNode, module) {
  var topic = {}
  topic.name = serviceNode.serviceId + '/' + module.type

  if (module.topic.retain) {
    topic.data = {}  //TODO load from userdata
    const validate = ajvSet.compile(module.schema)
    validate(topic.data)
  }

  topic.$find = findTopic
  topic.$update = updateTopic
  topic.$publish = publishTopic
  return extend(topic, module)
}

function initMethods(topic) {
  console.log('2')
  const methods = topic.methods
  if (methods) {
    for (const key in methods) {
      topic[key] = methods[key] == null ? utils.noop : utils.bind(methods[key], topic)
    }
  }
}

function Service(options) {
  console.log('1')
  this._init(options)
}

Service.prototype._init = function(options) {
  console.log('init')
  const service = _.find(templates, {
    type: options.serviceType + '/' + options.serviceVersion,
  })
  if (!service) {
    console.log('Can not find ' + options.serviceType)
    return
  }

  const serviceId = makeServiceId(options.serviceType, options.serviceVersion, options.serviceName)
  if (findServiceNode(serviceId)) {
    console.log('Service ' + serviceId + ' already created')
    return
  }

  const serviceNode = this
  serviceNode.serviceId = serviceId
  serviceNode.topics = []
  serviceNode.topicsOfProxy = []
  serviceNode.schema = service.schema

  _.forEach(service.modules, function(module) {
    module.type = module.id
    module.schema = module.topic.payloadSchema
    module.check  = ajvCheck.compile(module.schema)
    const topic = newTopic(serviceNode, module)
    if (module.topic.perUser) {
      serviceNode.topicsOfProxy.push(topic)
    } else {
      initMethods(topic)
      serviceNode.topics.push(topic)
    }
  })
  addServiceNode(serviceNode)

}

module.exports = Service